/*
 *  linux/fs/super.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * super.c contains code to handle the super-block tables.
 */
#include <linux/config.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/system.h>

#include <errno.h>
#include <sys/stat.h>

int sync_dev(int dev);
void wait_for_keypress(void);

/* set_bit uses setb, as gas doesn't recognize setc */
// 测试指定位偏移处比特位的值，并返回该原比特位值（应该取名为 set_bit() 更妥帖）
#define set_bit(bitnr,addr) ({ \
register int __res ; \
__asm__("bt %2,%3;setb %%al":"=a" (__res):"a" (0),"r" (bitnr),"m" (*(addr))); \
__res; })

// 超级块表（数组）
struct super_block super_block[NR_SUPER];
/* this is initialized in init/main.c */
// 根文件系统设备的设备号
int ROOT_DEV = 0;

// 锁定超级块（等待解锁后，上锁）
static void lock_super(struct super_block * sb)
{
	cli();
	while (sb->s_lock)
		sleep_on(&(sb->s_wait));
	sb->s_lock = 1;
	sti();
}

// 释放超级块（释放锁）
static void free_super(struct super_block * sb)
{
	cli();
	sb->s_lock = 0;
	wake_up(&(sb->s_wait));
	sti();
}

// 睡眠等待，直到超级块解锁
static void wait_on_super(struct super_block * sb)
{
	cli();
	while (sb->s_lock)
		sleep_on(&(sb->s_wait));
	sti();
}

// 取指定设备的超级块。
// 在超级块表（数组）中搜索指定设备 dev 的超级块结构信息。若找到，则返回超级块的指针， 否则返回空指针。
struct super_block * get_super(int dev)
{
	struct super_block * s;

	if (!dev)
		return NULL;
	s = 0+super_block;
	while (s < NR_SUPER+super_block)
		if (s->s_dev == dev) {
			wait_on_super(s);
			if (s->s_dev == dev)
				return s;
			// 再次从头开始查找
			s = 0+super_block;
		} else
			s++;
	return NULL;
}

// 释放（放回）指定设备的超级块。
// 释放设备所使用的超级块数组项（置 s_dev=0)，并释放该设备i节点位图和逻辑块位图所占用的高速缓冲块。
// 如果超级块对应的文件系统是根文件系统，或者其某个 i节点上已经安装有其他的文件系统，则不能释放该超级块。
void put_super(int dev)
{
	struct super_block * sb;
	/* struct m_inode * inode;*/
	int i;

	if (dev == ROOT_DEV) {
		printk("root diskette changed: prepare for armageddon\n\r");
		return;
	}
	if (!(sb = get_super(dev)))
		return;
	// 该文件系统所安装到的i节点（在文件系统卸载（umount）操作中， imount 会先被置成 Null 以后才会调用本函数。）
	if (sb->s_imount) {
		printk("Mounted disk changed - tssk, tssk\n\r");
		return;
	}
	lock_super(sb);
	// s_dev 设为0，即释放该设备上的文件系统超级块（即让超级块表中该表项为空闲项）
	sb->s_dev = 0;
	// 释放该设备上文件系统i节点位图和逻辑块位图在缓冲区中所占用的缓冲块
	for(i=0;i<I_MAP_SLOTS;i++)
		brelse(sb->s_imap[i]);
	for(i=0;i<Z_MAP_SLOTS;i++)
		brelse(sb->s_zmap[i]);
	free_super(sb);
	return;
}

// 读取指定设备的超级块。
// 如果指定设备 dev 上的文件系统超级块己经在超级块表中，则直接返回该超级块项的指针。
// 否则就从设备 dey 上读取超级块到缓冲块中，并复制到超级块表中。并返回超级块指针。操作失败返回NULL
static struct super_block * read_super(int dev)
{
	struct super_block * s;
	struct buffer_head * bh;
	int i,block;

	if (!dev)
		return NULL;
	check_disk_change(dev);
	// 如果该设备的超级块已经在超级块表中，则直接返回该超级块的指针。
	if ((s = get_super(dev)))
		return s;
	// 否则，首先在超级块数组中找出一个空项(也即字段s_dev=0 的项）。如果数组己经占满，则返回空指针。
	for (s = 0+super_block ;; s++) {
		if (s >= NR_SUPER+super_block)
			return NULL;
		if (!s->s_dev)
			break;
	}
	// 在超级块表中找到超级块空项后，进行如下操作
	s->s_dev = dev;
	s->s_isup = NULL;
	s->s_imount = NULL;
	s->s_time = 0;
	s->s_rd_only = 0;
	s->s_dirt = 0;
	lock_super(s);
	// 读取设备的1号逻辑块（超级块，0号块是引导块）
	if (!(bh = bread(dev,1))) {
		s->s_dev=0;
		free_super(s);
		return NULL;
	}
	// 超级块信息从缓冲块数据区复制到超级块数组相应项结构中
	*((struct d_super_block *) s) =
		*((struct d_super_block *) bh->b_data);
	// 释放掉临时的缓冲块
	brelse(bh);
	if (s->s_magic != SUPER_MAGIC) {
		s->s_dev = 0;
		free_super(s);
		return NULL;
	}
	// 清空位图缓冲块
	for (i=0;i<I_MAP_SLOTS;i++)
		s->s_imap[i] = NULL;
	for (i=0;i<Z_MAP_SLOTS;i++)
		s->s_zmap[i] = NULL;
	// 读取inode位图块和逻辑块位图块到缓冲区
	block=2; // 2号盘块是第1个inode位图块
	for (i=0 ; i < s->s_imap_blocks ; i++)
		if ((s->s_imap[i]=bread(dev,block)))
			block++;
		else
			break;
	for (i=0 ; i < s->s_zmap_blocks ; i++)
		if ((s->s_zmap[i]=bread(dev,block)))
			block++;
		else
			break;
	// 验证一下读取的缓冲块数量，验证失败则释放缓冲块
	if (block != 2+s->s_imap_blocks+s->s_zmap_blocks) {
		for(i=0;i<I_MAP_SLOTS;i++)
			brelse(s->s_imap[i]);
		for(i=0;i<Z_MAP_SLOTS;i++)
			brelse(s->s_zmap[i]);
		s->s_dev=0;
		free_super(s);
		return NULL;
	}
	// 将inode位图的0号比特置1，保留不用
	s->s_imap[0]->b_data[0] |= 1;
	// 将逻辑块位图的0号比特置1，保留不用
	s->s_zmap[0]->b_data[0] |= 1;
	// 释放锁
	free_super(s);
	return s;
}

// 卸载文件系统（系统调用）。
// 参数 dev_name 是文件系统所在设备的设备文件名。
// 该函数首先根据参数给出的块设备文件名获得设备号，然后复位文件系统超级块中的相应字段，释放超级块和位图占用的缓冲块，
// 最后对该设备执行高速缓冲与设备上数据的同步操作。
// 若卸载操作成功则返回0，否则返回出错码。
int sys_umount(char * dev_name)
{
	struct m_inode * inode;
	struct super_block * sb;
	int dev;

	// 通过设备名称找到设备号
	if (!(inode=namei(dev_name)))
		return -ENOENT;
	dev = inode->i_zone[0];
	// 如果设备文件不是块设备，则直接释放inode，并返回错误码
	if (!S_ISBLK(inode->i_mode)) {
		iput(inode);
		return -ENOTBLK;
	}
	// 为了得到设备号而取得的i节点 devi 已完成了它的使命
	iput(inode);
	// 对块设备文件进行处理（主要处理 挂载 相关的操作）
	// 如果设备上是根文件系统， 则不能被卸载，返回忙出错号。
	if (dev==ROOT_DEV)
		return -EBUSY;
	// 如果在超级块表中没有找到该设备上文件系统的超级块，或者己找到但是该设备上文件系统没有安装过，则返回出错码。
	if (!(sb=get_super(dev)) || !(sb->s_imount))
		return -ENOENT;
	// 如果超级块所指明的被安装到的i节点并没有置位其安装标志 imount，则显示警告信息。
	if (!sb->s_imount->i_mount)
		printk("Mounted inode has i_mount=0\n");
	// 然后查找一下i节点表，看看是否有进程在使用该设备上的文件，如果有，则返回忙出错码。
	for (inode=inode_table+0 ; inode<inode_table+NR_INODE ; inode++)
		if (inode->i_dev==dev && inode->i_count)
				return -EBUSY;
	// 开始正式umount操作
	// 1、挂载点的inode上的挂载标志（i_mount）复位
	sb->s_imount->i_mount=0;
	// 2、释放挂载点inode，并将超级块中的挂载点字段设置NULL
	iput(sb->s_imount);
	sb->s_imount = NULL;
	// 3、放回设备文件系统的根i节点，并将超级块中的挂载点字段设置NULL
	iput(sb->s_isup);
	sb->s_isup = NULL;
	// 4、释放超级块
	put_super(dev);
	// 5、同步设备缓冲块
	sync_dev(dev);
	return 0;
}

// 安装文件系统（系统调用）。
// 参数 dev_name 是设备文件名，dir_name 是安装到的目录名，rw_flag 被安装文件系统的可读写标志。
// 将被mount的地方必须是一个目录名，并且对应的i节点没有被其他程序占用。
// 若操作成功则返回0，否则返回出错号。
int sys_mount(char * dev_name, char * dir_name, int rw_flag)
{
	struct m_inode * dev_i, * dir_i;
	struct super_block * sb;
	int dev;

	if (!(dev_i=namei(dev_name)))
		return -ENOENT;
	dev = dev_i->i_zone[0];
	if (!S_ISBLK(dev_i->i_mode)) {
		iput(dev_i);
		return -EPERM;
	}
	// 为了得到设备号而取得的i节点 devi 已完成了它的使命
	iput(dev_i);
	if (!(dir_i=namei(dir_name)))
		return -ENOENT;
	// 目录文件名找到对应的i节点 dir_i。如果该i节点的引用计数不为1（仅在这里引用）， 
	// 或者该i节点的节点号是根文件系统的节点号1，则放回该i节点返回出错码。
	if (dir_i->i_count != 1 || dir_i->i_num == ROOT_INO) {
		iput(dir_i);
		return -EBUSY;
	}
	// 另外，如果该节点不是一个目录文件节点，则也放回该i节点，返回出错码。因为文件系统只能安装在一个目录名上。
	if (!S_ISDIR(dir_i->i_mode)) {
		iput(dir_i);
		return -EPERM;
	}
	if (!(sb=read_super(dev))) {
		iput(dir_i);
		return -EBUSY;
	}
	// 如果设备已经被挂载到其他地方
	if (sb->s_imount) {
		iput(dir_i);
		return -EBUSY;
	}
	// 如果挂载点已经被其他设备挂载
	if (dir_i->i_mount) {
		iput(dir_i);
		return -EPERM;
	}
	// 正式挂载操作
	// 1、设置超级块的挂载点
	sb->s_imount=dir_i;
	// 2、设置挂载点inode的挂载标志
	dir_i->i_mount=1;
	// 3、挂载点inode发生修改，设置脏位置位（注意并不会虚入磁盘，而是在umount中复位）
	dir_i->i_dirt=1;		/* NOTE! we don't iput(dir_i) */
	return 0;			/* we do that in umount */
}

// 安装根文件系统。(该函数属于系统初始化操作的一部分。该函数会在系统开机进行初始化设置时 sys_setup(）中被调用)
// 函数首先初始化文件表数组 file_table[]和超级块表 (数组），
// 然后读取根文件系统超级块，并取得文件系统根i节点。
// 最后统计并显示出根文件系统上的可用资源（空闲块数和空闲i节点数）。
void mount_root(void)
{
	int i,free;
	struct super_block * p;
	struct m_inode * mi;

	if (32 != sizeof (struct d_inode))
		panic("bad i-node size");
	// 初始化文件表数组
	for(i=0;i<NR_FILE;i++)
		file_table[i].f_count=0;
	// 如果根文件系统所在设备是软盘的话，就提示“插入根文件系统盘， 并按回车键”，并等待按键。
	if (MAJOR(ROOT_DEV) == 2) {
		printk("Insert root floppy and press ENTER");
		wait_for_keypress();
	}
	// 初始化超级块表
	for(p = &super_block[0] ; p < &super_block[NR_SUPER] ; p++) {
		p->s_dev = 0;
		p->s_lock = 0;
		p->s_wait = NULL;
	}
	// 读取根设备的超级块和设备根目录inode
	if (!(p=read_super(ROOT_DEV)))
		panic("Unable to mount root");
	if (!(mi=iget(ROOT_DEV,ROOT_INO)))
		panic("Unable to read root i-node");
	mi->i_count += 3 ;	/* NOTE! it is logically used 4 times, not 1 从逻辑上讲，它被引用了4次（下面3句），而不是1次 */
	p->s_isup = p->s_imount = mi;
	// 此时当前进程是1号进程 （init 进程）当前目录、根目录
	current->pwd = mi;
	current->root = mi;
	// 然后我们对根文件系统上的资源作统计工作。统计该设备上空闲块数和空闲i节点数。
	// 首先令i等于超级块中表明的设备逻辑块总数。然后根据逻辑块位图中相应比特位的占用情况统计出空闲块数。
	// 这里宏函数 set bit(）只是在测试比特位，而非设置比特位。
	free=0;
	i=p->s_nzones;
	while (-- i >= 0)
		if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))
			free++;
	printk("%d/%d free blocks\n\r",free,p->s_nzones);
	free=0;
	i=p->s_ninodes+1;
	while (-- i >= 0)
		if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))
			free++;
	printk("%d/%d free inodes\n\r",free,p->s_ninodes);
}
